Saltar al contenido
Nuevo

DeepSeek V4.1 Flash gratis por 7 días: 10M tokens al día tras cualquier recarga. Hasta el 30/11.

Cómo funciona

Cómo funciona HTTP: solicitudes, respuestas, encabezados y códigos de estado

Lee un intercambio HTTP real línea por línea: métodos, URL, encabezados, clases de estado y cookies, probados con curl en un servidor local.

Principiante11 min de lecturaPor NotHoly

Qué es HTTP

HTTP es el protocolo que usan navegadores, apps y APIs para intercambiar mensajes. La RFC 9110 define qué significan esos mensajes: métodos, códigos de estado, campos de encabezado y demás. La RFC 9112 define cómo se ve un mensaje HTTP/1.1 como texto plano sobre una conexión TCP. La descripción general de MDN es una introducción más corta, orientada al navegador, y encaja bien junto a las RFC.

El patrón es sencillo. Un cliente envía una solicitud y un servidor devuelve una respuesta. Siempre es el cliente quien inicia el intercambio. HTTP sin más no cifra nada y no recuerda quién eres entre una solicitud y otra. El cifrado viene de TLS, que explica el siguiente artículo. La memoria entre solicitudes viene de las cookies y los tokens, que veremos más abajo.

Por qué importa

Casi todo el comportamiento web que vas a estudiar pasa por HTTP. Un formulario envía un valor en el cuerpo, una API confía en un encabezado, una redirección lleva al usuario a otro lugar y una caché guarda la copia equivocada de una página. Cuando después uses un proxy como OWASP ZAP para inspeccionar tráfico, te mostrará justamente estos mensajes. Si no puedes leer una solicitud en bruto, la salida del proxy resulta difícil de aprovechar.

La forma de un mensaje

Una solicitud HTTP/1.1 es texto plano con esta forma:

http
GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: curl/8.12.1
Accept: */*

La primera línea es la línea de solicitud: el método, el destino de la solicitud y la versión del protocolo. Cada línea siguiente es un campo de encabezado, escrito como nombre: valor. Una línea vacía termina la sección de encabezados. Si el mensaje tiene cuerpo, aparece después de esa línea vacía. En el cable, cada línea termina en CRLF (un retorno de carro y un salto de línea), algo que el editor oculta.

Una respuesta tiene el mismo formato, con una línea de estado al inicio: versión, código de estado y frase de motivo.

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 15

<h1>hello</h1>

Métodos

El método indica lo que el cliente quiere que se haga. La RFC 9110 define GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS y TRACE. PATCH viene de la RFC 5789.

Al leer tráfico importan dos propiedades:

  • Métodos seguros (GET, HEAD, OPTIONS, TRACE): desde el punto de vista del cliente, deben ser de solo lectura. RFC 9110, sección 9.2.1.
  • Métodos idempotentes (GET, HEAD, PUT, DELETE, OPTIONS, TRACE): pueden repetirse con el mismo efecto previsto que una sola solicitud. RFC 9110, sección 9.2.2. POST no es ni seguro ni idempotente.

Estas propiedades son promesas sobre el significado. Quien decide qué pasa de verdad es el servidor. Un manejador de GET que borra un registro rompe ese contrato, y un revisor de seguridad buscará justo ese error.

URL y destino de la solicitud

Toma la URL http://127.0.0.1:8000/missing?lang=en#top y divídela en partes:

  • Esquema: http, que indica al cliente qué protocolo usar.
  • Autoridad: 127.0.0.1:8000, el host y el puerto opcional.
  • Ruta: /missing.
  • Query: lang=en, después del ?.
  • Fragmento: top, después del #.

El cliente no envía al servidor ni el esquema ni el fragmento. La línea de solicitud lleva la ruta y la query. La autoridad va en el encabezado Host. HTTP/1.1 exige Host porque muchos sitios pueden compartir una misma IP, y el servidor usa ese encabezado para decidir qué sitio debe responder.

Encabezados

Los nombres de encabezado no distinguen mayúsculas de minúsculas, así que Content-Type y content-type son el mismo campo (RFC 9110, sección 5.1). Los valores son otra cosa: muchos sí distinguen mayúsculas, así que no des por hecho que no lo hacen.

Encabezados de solicitud que verás con más frecuencia:

  • Host: obligatorio en toda solicitud HTTP/1.1. La RFC 9112, sección 3.2, dice que el servidor debe responder 400 si falta.
  • User-Agent: el software del cliente.
  • Accept: los tipos de medio que el cliente puede manejar.
  • Cookie: las cookies que el navegador devuelve al servidor.
  • Authorization: credenciales, a menudo un token bearer.
  • Content-Type y Content-Length: describen el cuerpo, cuando lo hay.

Encabezados de respuesta que verás con más frecuencia:

  • Content-Type: el tipo de medio del cuerpo, como text/html.
  • Content-Length: el tamaño del cuerpo en bytes.
  • Date: el reloj del servidor, en GMT.
  • Server: el nombre del software. Muchos servidores lo muestran. En producción suele ocultarse.
  • Last-Modified: cuándo cambió por última vez el recurso según el servidor.
  • Set-Cookie: pide al navegador que guarde una cookie.

Los encabezados son datos que controla quien los envía. El navegador manda lo que manda su software, y un script o un proxy puede enviar lo que quiera. X-Forwarded-For es el ejemplo clásico: un proxy lo agrega, pero un cliente también puede enviarlo. Nunca confíes en un encabezado solo porque parezca oficial.

Códigos de estado

Un código de estado tiene tres dígitos. El primer dígito indica la clase, definida en la RFC 9110, sección 15:

Clase Significado Ejemplos
1xx Informativo. La solicitud se recibió y el proceso continúa 100 Continue
2xx Éxito 200 OK, 201 Created, 204 No Content
3xx Redirección. El cliente debe dar otro paso 301 Moved Permanently, 304 Not Modified
4xx Error del cliente. La solicitud tiene un problema 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed
5xx Error del servidor. No pudo atender una solicitud válida 500, 501 Not Implemented, 502, 503

Aunque un cliente no reconozca un código concreto, puede actuar según la clase. Un 4xx indica que la solicitud debe cambiar. Un 5xx indica que la misma solicitud podría funcionar más tarde.

Dos códigos se confunden con frecuencia. Un 401 significa que la solicitud no tiene credenciales válidas. Un 403 significa que el servidor entendió la solicitud y la rechaza, incluso con credenciales.

La frase de motivo, como Not Found, es un texto que elige el servidor. La RFC 9112, sección 4, dice que el cliente debe ignorarla. Escribe la lógica sobre el número, nunca sobre las palabras.

Cookies

Las cookies no forman parte de la RFC 9110. Se definen en la RFC 6265. El ciclo funciona así:

  1. El servidor envía Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax.
  2. El navegador guarda el valor junto con sus atributos.
  3. En solicitudes posteriores a un sitio que coincide, el navegador envía Cookie: session=abc123.

Una cookie de sesión es un identificador que entrega el servidor. No es una contraseña y no es privada del usuario. Quien tenga el valor normalmente puede actuar como esa sesión hasta que expire o se revoque. HttpOnly impide que los scripts de la página lean el valor. Secure permite enviarla solo por HTTPS. SameSite limita cuándo el navegador la envía en solicitudes entre sitios. Los artículos de seguridad web volverán sobre estos atributos.

Laboratorio práctico

Necesitas curl y Python 3. Git Bash en Windows 11 incluye curl, y la mayoría de sistemas Linux y macOS también lo tienen. El servidor es http.server, que viene integrado en Python. Ejecútalo solo en tu máquina, enlazado a 127.0.0.1, y nunca lo expongas a una red.

La salida de abajo proviene de ejecutar estos comandos en Windows 11 con Git Bash, curl 8.12.1 y Python 3.12.10. En esta máquina python3 no estaba en el PATH de Git Bash, así que el servidor se inició con la ruta completa al ejecutable de Python 3.12. Usa python3, py -3 o python, según tu sistema. Tus fechas, horas y versiones serán distintas. La barra de progreso de curl se quitó de la salida, y los cuerpos HTML largos se acortaron.

Paso 1: crear el sitio e iniciar el servidor

En la primera terminal:

bash
mkdir httplab && cd httplab && echo '<h1>hello</h1>' > index.html
python3 -m http.server 8000 --bind 127.0.0.1

Deja esta terminal abierta. El servidor escribe una línea de registro por cada solicitud, así que cada solicitud que envíes abajo aparecerá aquí.

Paso 2: leer un intercambio completo

En la segunda terminal:

bash
curl -v http://127.0.0.1:8000/

Salida:

text
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> 
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:16:48 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 
<h1>hello</h1>
* shutting down connection #0

Léelo en este orden:

  • Las líneas que empiezan con * son notas de curl sobre la conexión.
  • Las líneas que empiezan con > son lo que curl envió. Curl añadió por su cuenta Host, User-Agent y Accept.
  • Las líneas que empiezan con < son lo que devolvió el servidor.
  • La línea > vacía termina los encabezados de la solicitud. La línea < vacía termina los encabezados de la respuesta.
  • <h1>hello</h1> es el cuerpo. Content-Length: 15 coincide con el archivo, porque echo añadió un salto de línea después de la etiqueta.
  • HTTP 1.0, assume close after body muestra una discrepancia. Curl pidió HTTP/1.1, pero el servidor respondió con HTTP/1.0. El servidor simple de Python habla HTTP/1.0, así que curl cierra la conexión cuando termina de leer el cuerpo.

Paso 3: pedir un archivo que no existe

bash
curl -i http://127.0.0.1:8000/missing

La opción -i muestra los encabezados de la respuesta junto con el cuerpo. Salida, con el cuerpo HTML acortado:

text
HTTP/1.0 404 File not found
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 335

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 404</p>
        <p>Message: File not found.</p>
        <p>Error code explanation: 404 - Nothing matches the given URI.</p>
    </body>
</html>

Mira la primera terminal. Registró:

text
127.0.0.1 - - [08/Nov/2026 05:16:49] code 404, message File not found
127.0.0.1 - - [08/Nov/2026 05:16:49] "GET /missing HTTP/1.1" 404 -

El código de estado es 404, un código de la clase de error del cliente: la ruta no corresponde a ningún recurso. La frase de motivo File not found es el texto que eligió el propio servidor. El cuerpo es una página HTML que el servidor generó para el error. Cualquier estado puede llevar cualquier cuerpo, así que lee ambos.

El registro muestra las 05:16 y el encabezado Date muestra las 08:16. El registro usa la hora local y el encabezado usa GMT, así que los dos difieren en el desfase horario local.

Paso 4: enviar un método que el servidor no implementa

bash
curl -i -X POST http://127.0.0.1:8000/

Salida, con el cuerpo HTML acortado:

text
HTTP/1.0 501 Unsupported method ('POST')
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 357

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('POST').</p>
        <p>Error code explanation: 501 - Server does not support this operation.</p>
    </body>
</html>

La línea de registro dice code 501, message Unsupported method ('POST'). La opción -X cambia solo el método. Curl envió los mismos encabezados que antes.

El servidor simple no tiene ningún manejador de POST, así que 501 Not Implemented es la respuesta honesta. La RFC 9110, sección 15.6.2, define el 501 para un servidor que no ofrece la funcionalidad que la solicitud necesita. Una aplicación real que conoce el recurso pero rechaza POST en él normalmente respondería 405 Method Not Allowed. Una respuesta 405 debe incluir un encabezado Allow con los métodos que acepta el recurso.

Paso 5: pedir solo encabezados con HEAD

bash
curl -I http://127.0.0.1:8000/index.html

Salida:

text
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:50 GMT
Content-type: text/html
Content-Length: 15
Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT

La opción -I envía una solicitud HEAD. El estado y los encabezados coinciden con los que devolvería un GET, pero no llega ningún cuerpo. Content-Length sigue diciendo 15, porque describe la representación que devolvería un GET. La RFC 9110, sección 9.3.2, define este comportamiento. El registro muestra "HEAD /index.html HTTP/1.1" 200 -.

HEAD sirve cuando quieres saber si un archivo existe, cuánto pesa o cuándo cambió por última vez, sin descargarlo.

bash
curl -v -H 'Cookie: session=demo123' http://127.0.0.1:8000/

Las líneas relevantes de la salida:

text
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> Cookie: session=demo123
> 
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:18:28 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 

La línea del cuerpo se omite aquí. El servidor respondió con el mismo 200 de antes, porque no tiene lógica de cookies e ignora el encabezado. Ese es el sentido del ejercicio. El navegador envía cookies, pero quien decide qué significan es el servidor. Un servidor que nunca define una cookie no va a actuar sobre una que tú envíes.

Paso 7: detener el servidor

Pulsa Ctrl+C en la primera terminal cuando termines.

Errores comunes

  • Leer 200 como éxito. Un 200 puede traer una página de error. Revisa el cuerpo y lo que hizo la aplicación.
  • Tratar GET como inofensivo. Seguro describe la intención del cliente. Si un GET modifica datos, el servidor está mal, y los atacantes lo intentarán.
  • Escribir lógica sobre la frase de motivo. El cliente debe usar el número. El servidor puede cambiar las palabras cuando quiera.
  • Confiar en los encabezados del cliente. Host, User-Agent, Cookie y X-Forwarded-For vienen todos del lado del cliente. Valídalos en el servidor.
  • Suponer que HTTP es privado. HTTP sin cifrar lo puede leer cualquiera en el camino entre tú y el servidor. Por eso los sitios reales usan HTTPS. El laboratorio solo está bien porque el tráfico nunca sale de 127.0.0.1.
  • Esperar que -X cambie los encabezados. Cambia solo el método. Para añadir encabezados usa -H.

Cómo seguir

  • Lee la RFC 9110, sección 9 (métodos) y sección 15 (códigos de estado). Es larga, así que revisa solo las partes que necesites.
  • Lee la RFC 9112 para el enmarcado de los mensajes: cómo cliente y servidor se ponen de acuerdo sobre dónde termina el cuerpo, incluidos Content-Length y Transfer-Encoding.
  • Lee la descripción general de HTTP de MDN para ver los mismos conceptos desde el navegador.
  • Después, qué es HTTPS y TLS muestra el mismo intercambio con cifrado. Si aún no lo leíste, qué es un puerto explica la parte :8000 de la URL.

Un proxy como ZAP muestra estos mismos mensajes, y por eso este artículo sirve de base para los siguientes, sobre vulnerabilidades web. La API de NotHoly, en api.notholy.com/v1, también es una API HTTP con cuerpos JSON, servida sobre TLS, así que los encabezados y códigos de estado de arriba se aplican cuando la llamas.

Fuentes

  1. RFC 9110: HTTP Semantics
  2. RFC 9112: HTTP/1.1
  3. MDN Web Docs: An overview of HTTP
  4. RFC 6265: HTTP State Management Mechanism (cookies)

Más en Redes y web

Redes y web

¿Qué es TLS? Cómo HTTPS protege el tráfico

TLS 1.3 aporta a HTTPS confidencialidad, integridad y autenticación del servidor. Explica el handshake, los certificados y lo que el candado no prueba.

Principiante12 min de lectura