HTTP y cliente-servidor: qué ocurre cuando una aplicación pide datos
«El servidor funciona» es una frase estupenda hasta que alguien pregunta qué ha contestado. Puede haber respondido correctamente, haber rechazado la operación o haber devuelto una página de error que el cliente intenta interpretar como inventario. Encender un servicio y recibir los datos esperados son dos hechos distintos. Me gustaría dar por superada esta diferencia, pero la cantidad de botones que siguen girando después de un error sugiere que todavía tiene recorrido profesional.
Vamos a seguir una petición desde una aplicación cliente hasta un servidor y de vuelta. Usaremos un inventario ficticio de espacios y dos archivos Python: uno atiende las peticiones y otro muestra las respuestas. Todo ocurrirá en tu ordenador. Podremos observar un resultado correcto, una dirección inexistente y una operación rechazada, sin cuentas externas ni credenciales. Después será más sencillo entender qué parte falla cuando la aplicación deje de parecer tan educada.
Cliente y servidor son responsabilidades, no tamaños de ordenador
El cliente inicia una petición y el servidor la atiende. Un navegador puede ser cliente de una web; un script puede ser cliente de una API; un backend puede recibir una petición del navegador y convertirse a su vez en cliente de otro servicio. La palabra «servidor» no exige un armario con luces. En nuestro ejercicio será un proceso Python escuchando en una dirección local.
HTTP proporciona las reglas para expresar peticiones y respuestas. La introducción de MDN explica este intercambio y la participación de intermediarios. En una instalación real puede haber un proxy o una caché entre las dos partes; la respuesta que recibes no tiene por qué proceder directamente del proceso que imaginabas. Aquí reduciremos el recorrido a dos procesos locales para ver las piezas con claridad.
Una petición identifica qué operación se solicita y sobre qué dirección. La respuesta incluye un código de estado, cabeceras y, cuando corresponde, un cuerpo. El cuerpo puede contener JSON, HTML, una imagen u otro formato. HTTP no convierte cualquier contenido en datos BIM por el mero hecho de transportarlo. El servidor y el cliente tendrán que compartir además un acuerdo sobre la estructura y el significado del inventario.
Es útil separar también HTTP de HTTPS. HTTPS utiliza HTTP sobre una conexión protegida con TLS: protege el transporte y permite verificar la identidad del servidor bajo las condiciones de esa conexión. No decide si el usuario tiene permiso para ver un proyecto. Nuestro laboratorio utilizará HTTP sin cifrar solo en 127.0.0.1, la dirección de bucle local. No es una propuesta de despliegue para datos de clientes.
Leer una petición antes de culpar a la conexión
La dirección del ejercicio será http://127.0.0.1:8765/inventario. El esquema es http; el equipo es tu propia máquina; 8765 es el puerto donde escuchará el proceso; /inventario es la ruta que reconocerá nuestra aplicación. Dos procesos no pueden apropiarse a la vez de esa misma dirección y puerto. Si el arranque indica que ya está en uso, no cierres servicios al azar: cambia 8765 en ambos archivos por otro puerto local libre.
El método expresa la intención de la petición. Usaremos GET para recuperar una representación del inventario. Más adelante enviaremos POST para comprobar qué ocurre cuando pedimos una operación que este pequeño servicio no permite. El método forma parte de la petición; compartir la misma URL no convierte ambas solicitudes en la misma acción. Es uno de esos detalles que parece secundario hasta que alguien conecta un botón de guardar a una operación de lectura.
Las cabeceras añaden información sobre el intercambio. Nuestro cliente enviará Accept: application/json para expresar el formato que desea recibir. El servidor responderá con Content-Type: application/json; charset=utf-8 para describir lo que realmente envía. Pedir JSON no garantiza recibirlo: hay que mirar la respuesta. Cuando mandemos un cuerpo JSON con POST, su propia cabecera Content-Type describirá ese cuerpo de petición, no la respuesta futura.
Los códigos de estado tampoco son decoración. En este caso, 200 indicará que hemos recuperado el inventario, 404 que esa ruta no está disponible y 405 que la operación solicitada no está admitida. Para 405 enviaremos además Allow: GET. La semántica de HTTP define estas piezas; los detalles concretos del contenido siguen dependiendo de nuestra aplicación. Un 200 con una colección vacía puede ser una respuesta perfectamente válida y no equivale a un 404.
Levantar un inventario local que responda de verdad
Necesitas Python 3.10 o posterior; no instalaremos paquetes. Guarda este archivo como servidor_http.py. La clase define las respuestas para GET y POST. El inventario vive en memoria y no hay ninguna conexión con Revit, IFC o Autodesk APS. Los identificadores que ves son datos ficticios del ejercicio.
Abre una terminal en esa carpeta y ejecútalo:
La terminal permanecerá ocupada porque el proceso espera peticiones. No ha terminado ni se ha quedado bloqueado por un error: ese es su trabajo. Puedes abrir http://127.0.0.1:8765/inventario en un navegador y ver el JSON. Deja el servidor abierto para el siguiente paso; para detenerlo, vuelve a su terminal y pulsa Ctrl+C.
La respuesta calcula Content-Length a partir de los bytes UTF-8, no de la longitud del texto anterior a codificarlo. Ambos tamaños pueden diferir con caracteres acentuados. También indicamos que cerraremos la conexión tras la respuesta: así el ejemplo evita introducir la reutilización de conexiones mientras estamos aprendiendo a distinguir una petición de su contenido.
La ruta se compara de forma literal. /inventario/ o /inventario?nivel=N00 recibirán 404, porque no hemos implementado variantes ni filtros. POST se rechaza sin procesar su cuerpo y sin cambiar el inventario. Otros métodos no están implementados por esta clase y la biblioteca puede responder con su error predeterminado; no prometemos un formato JSON para cualquier petición imaginable. La biblioteca http.server está pensada aquí para aprendizaje local y no se recomienda como servidor de producción.
Observar el estado, las cabeceras y el cuerpo desde el cliente
Guarda este segundo archivo como cliente_http.py. Enviará tres solicitudes: la consulta válida, una ruta inexistente y un intento de POST con un pequeño cuerpo JSON. El inventario seguirá teniendo los mismos dos espacios. Este cliente desactiva el uso de proxies configurados en el entorno porque su destino es exclusivamente el servidor local del ejercicio.
En otra terminal, con el servidor todavía activo, ejecuta:
Verás los estados 200, 404 y 405, en ese orden. La primera respuesta contendrá el proyecto y los dos espacios; la segunda explicará que no se encuentra la ruta; la tercera rechazará la operación y mostrará Allow: GET. En la terminal del servidor aparecerá también una línea por petición, con el método, la ruta y el estado. Están ocurriendo intercambios HTTP entre procesos, aunque ambos estén en la misma máquina.
La biblioteca urllib.request permite construir la petición y leer la respuesta. Para respuestas HTTP de error, este cliente captura HTTPError y conserva su estado, sus cabeceras y su cuerpo. Esa distinción importa: recibir un 404 significa que ha llegado una respuesta HTTP; no es lo mismo que no lograr conectar. El cliente imprime el motivo estructurado de nuestro servidor en vez de esconderlo detrás de un genérico «algo ha ido mal».
Aquí interpretamos el cuerpo como JSON porque controlamos ambas partes y hemos definido ese formato para estos tres casos. Un cliente general necesitaría comprobar el tipo y tratar cuerpos vacíos, HTML u otros contenidos, además de errores de interpretación. La explicación de JSON y datos anidados ayuda con esa segunda capa: haber recibido bytes no demuestra que su estructura corresponda al inventario esperado.
Cuando falla, identifica hasta dónde ha llegado la petición
Prueba a detener el servidor y ejecutar de nuevo el cliente. Ahora las tres solicitudes deberían indicar que no han obtenido respuesta HTTP; el nombre concreto del error puede variar según el sistema. No hay un estado 404 esperando detrás del puerto cerrado. El proceso que tenía que atender la petición ya no está escuchando. Esa diferencia permite decidir si investigar el arranque y la conexión o la dirección que hemos solicitado a un servicio que sí contesta.
Un tiempo de espera tampoco demuestra por sí solo que una operación no haya ocurrido. En este laboratorio POST siempre se rechaza; en un servicio que modificase datos, perder la respuesta después de enviar la petición dejaría una duda que habría que resolver antes de repetirla. Por eso una integración necesita distinguir el estado conocido del resultado de una comunicación incompleta. «No vi el mensaje de éxito» es información sobre el cliente, no una prueba universal de que el servidor no haya trabajado.
En una aplicación BIM, el siguiente paso será acordar recursos, permisos, estructura de datos y operaciones. HTTP proporciona el intercambio, pero no el catálogo de espacios ni la autorización para consultarlo. Recibir un inventario tampoco significa que esté actualizado, que sus identificadores correspondan al modelo abierto o que la respuesta incluya todos los elementos. Esas condiciones pertenecen al contrato de la aplicación y habrá que hacerlas visibles cuando construyamos ese contrato.
Para empezar a investigar, yo pediría la URL, el método, el estado recibido y un cuerpo de respuesta sin datos sensibles. Con esas cuatro cosas podemos abandonar la adivinación bastante pronto. Si compartes en los comentarios un caso del ejercicio que te haya sorprendido, cuenta si el servidor estaba activo y qué estado viste. Una captura del botón girando tiene mucho sentimiento, pero todavía no nos dice qué ha contestado nadie.
Respuestas