← Volver al listado de tecnologías

Capítulo 5: Cliente HTTP

Por: SiempreListo
pythonzighttpavanzado

Cliente HTTP

Un cliente que realiza peticiones HTTP y procesa respuestas JSON.

Conceptos que Aprenderás

Python

import json
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError
from dataclasses import dataclass
from typing import Optional, Any

@dataclass
class Respuesta:
    status: int
    body: Any
    error: Optional[str] = None

class ClienteHTTP:
    def __init__(self, base_url: str):
        self.base_url = base_url.rstrip('/')

    def get(self, path: str) -> Respuesta:
        url = f"{self.base_url}{path}"
        try:
            req = Request(url, headers={'Accept': 'application/json'})
            with urlopen(req, timeout=10) as resp:
                body = json.loads(resp.read().decode())
                return Respuesta(status=resp.status, body=body)
        except HTTPError as e:
            return Respuesta(status=e.code, body=None, error=str(e))
        except URLError as e:
            return Respuesta(status=0, body=None, error=str(e.reason))

    def post(self, path: str, data: dict) -> Respuesta:
        url = f"{self.base_url}{path}"
        try:
            body = json.dumps(data).encode()
            req = Request(url, data=body, headers={
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            })
            with urlopen(req, timeout=10) as resp:
                response_body = json.loads(resp.read().decode())
                return Respuesta(status=resp.status, body=response_body)
        except HTTPError as e:
            return Respuesta(status=e.code, body=None, error=str(e))
        except URLError as e:
            return Respuesta(status=0, body=None, error=str(e.reason))

def main():
    cliente = ClienteHTTP("https://jsonplaceholder.typicode.com")

    print("Obteniendo posts...")
    resp = cliente.get("/posts/1")
    if resp.error:
        print(f"Error: {resp.error}")
    else:
        print(f"Status: {resp.status}")
        print(f"Título: {resp.body['title']}")

    print("\nCreando post...")
    nuevo = cliente.post("/posts", {
        "title": "Mi post",
        "body": "Contenido del post",
        "userId": 1
    })
    if nuevo.error:
        print(f"Error: {nuevo.error}")
    else:
        print(f"Creado con ID: {nuevo.body['id']}")

if __name__ == "__main__":
    main()

Zig

const std = @import("std");

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

fn httpGet(allocator: std.mem.Allocator, url: []const u8) !Respuesta {
    const uri = try std.Uri.parse(url);

    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    var buf: [4096]u8 = undefined;
    var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf });
    defer req.deinit();

    try req.send();
    try req.wait();

    const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);

    return Respuesta{
        .status = @intFromEnum(req.status),
        .body = body,
    };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const stdout = std.io.getStdOut().writer();

    try stdout.print("Obteniendo datos...\n", .{});

    const resp = httpGet(allocator, "https://jsonplaceholder.typicode.com/posts/1") catch |err| {
        try stdout.print("Error de conexión: {}\n", .{err});
        return;
    };
    defer allocator.free(resp.body);

    try stdout.print("Status: {d}\n", .{resp.status});
    try stdout.print("Body (primeros 200 chars):\n{s}\n", .{
        if (resp.body.len > 200) resp.body[0..200] else resp.body,
    });
}

Diferencias Clave

AspectoPythonZig
HTTPurllib/requestsstd.http.Client
JSONjson.loads()Parsing manual
ErroresExcepcionesError unions
TLSAutomáticoRequiere config

Ejercicios

  1. Implementa PUT y DELETE
  2. Agrega reintentos automáticos
  3. Cachea respuestas en memoria

← Anterior | Siguiente →