← Volver al listado de tecnologías
Capítulo 5: Cliente HTTP
Cliente HTTP
Un cliente que realiza peticiones HTTP y procesa respuestas JSON.
Conceptos que Aprenderás
- Protocolo HTTP básico
- Sockets y conexiones TCP
- Parsing de respuestas
- Manejo de errores de red
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
| Aspecto | Python | Zig |
|---|---|---|
| HTTP | urllib/requests | std.http.Client |
| JSON | json.loads() | Parsing manual |
| Errores | Excepciones | Error unions |
| TLS | Automático | Requiere config |
Ejercicios
- Implementa PUT y DELETE
- Agrega reintentos automáticos
- Cachea respuestas en memoria