12k
All articles

HTTP incorpora un nuevo verbo para búsquedas

Conoce HTTP QUERY, un método seguro e idempotente para búsquedas con cuerpo. Revisa cómo funcionan la caché y los reintentos, qué clientes lo admiten y dónde se bloquea.

OpenReplay Team
OpenReplay Team
HTTP incorpora un nuevo verbo para búsquedas

El método HTTP QUERY, definido en la RFC 10008 en junio de 2026, coloca la consulta en el cuerpo de la solicitud, igual que POST, pero es seguro e idempotente como GET. Esto permite almacenar las respuestas en caché y reintentar automáticamente una solicitud fallida.

La mayoría de los equipos de API conocen bien el endpoint de búsqueda que empieza como un GET ordenado y termina convertido en una URL con filtros anidados, rangos de fechas y claves de ordenación codificados en ella. Luego alguien lo migra a POST, y la caché y los reintentos dejan de funcionar.

Este artículo explica qué problema resuelve QUERY, cómo es un intercambio QUERY a nivel de protocolo y qué partes del stack lo aceptan y cuáles todavía lo rechazan.

Puntos clave

  • QUERY es un método HTTP seguro e idempotente que transporta la consulta en el cuerpo de la solicitud. Está estandarizado en la RFC 10008 (junio de 2026).
  • Un servidor debe rechazar una solicitud QUERY cuyo Content-Type falte o no se corresponda con el cuerpo. Las cachés que almacenan respuestas QUERY incluyen el cuerpo de la solicitud en la clave de caché.
  • Los scripts pueden enviar QUERY con fetch(), pero un formulario HTML con method="query" recurre a GET y descarta el cuerpo.
  • Una solicitud QUERY de origen cruzado desde un navegador siempre desencadena una solicitud de verificación previa (preflight) de CORS, porque QUERY no es un método CORS-safelisted.
  • Cualquier servidor, proxy o WAF que no reconozca el método puede rechazarlo antes de que la solicitud llegue a tu aplicación.

¿Por qué los endpoints de búsqueda quedan atrapados entre GET y POST?

Ni GET ni POST funcionan bien para búsquedas complejas. GET coloca todos los filtros en la URL. POST traslada los filtros al cuerpo, pero indica a todos los intermediarios que la solicitud podría modificar el estado.

La RFC 9110 exige que todo emisor y receptor admita URI de al menos 8000 octetos. Se trata de un mínimo, no de un máximo, y HTTP no establece ningún límite superior, por lo que cualquier proxy, gateway o servidor en la ruta puede rechazar una URL más larga. Además, las query strings largas acaban en el historial del navegador, en los logs del servidor y en los marcadores, lo que expone cualquier dato que contengan los filtros.

POST evita esos problemas, pero no es ni seguro ni idempotente. Una solicitud idempotente produce el mismo efecto previsto tanto si se envía una vez como diez. La RFC 9110 indica a los clientes que no reintenten automáticamente una solicitud no idempotente, salvo que dispongan de algún modo de saber que en la práctica sí lo es. En consecuencia, las cachés generalmente no reutilizan las respuestas POST y los gateways no reintentan un POST que falla a mitad de camino.

¿Qué es el método HTTP QUERY?

Según la RFC 10008, una solicitud QUERY pide al servidor que ejecute la consulta descrita en el cuerpo de la solicitud y devuelva el resultado, sin modificar nada en el servidor. Gracias a ello, un cliente o un proxy puede reenviar o reiniciar una solicitud QUERY sin preocuparse de que un primer intento inconcluso haya alterado el estado, algo que no puede suponer con seguridad en el caso de POST. Así se ve la solicitud a nivel de protocolo (consulta la anatomía de una solicitud HTTP para conocer sus partes):

QUERY /products/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json

{"category": "laptops", "price": {"max": 1500}, "brands": ["acme", "globex"], "sort": "price_asc"}

La consulta queda definida por el cuerpo junto con su tipo de medio (media type). Si la cabecera Content-Type falta o no se corresponde con el contenido del cuerpo, la RFC 10008 exige que el servidor rechace la solicitud. Las indicaciones sobre códigos de estado de la RFC 10008 apuntan a un código 4xx, como 400 cuando no se especifica ningún tipo de medio, y 415 cuando el recurso no admite el tipo de medio enviado.

Una respuesta podría tener este aspecto:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60
Accept-Query: application/json
Content-Location: /products/search/results/7f3a
Location: /products/search/queries/7f3a

{"results": [ ... ]}

Cada cabecera cumple una función concreta:

  • Clave de caché: las respuestas QUERY pueden almacenarse en caché. Una caché que las almacene incluye el cuerpo de la solicitud en la clave de caché, porque dos solicitudes QUERY a la misma URL con cuerpos distintos plantean preguntas distintas.
  • Accept-Query: el servidor envía esta cabecera de respuesta para enumerar los formatos de consulta que acepta, expresados como una lista de Structured Fields de tipos de medio. Un mismo valor se aplica a todas las URI del servidor que compartan la misma ruta, independientemente de la query string.
  • Content-Location y Location: en una respuesta QUERY, Content-Location identifica el resultado de esa consulta concreta. Location proporciona una URI para la propia consulta, que el cliente puede recuperar más adelante con un GET normal. El artículo sobre qué contiene una respuesta HTTP explica cómo se comportan habitualmente estas cabeceras.

En el ejemplo, Cache-Control es una configuración de caché convencional. La RFC 10008 no la exige.

¿Dónde funciona QUERY actualmente?

El método HTTP QUERY ya funciona en varios runtimes de servidor, frameworks y clientes HTTP, siempre que nada entre el cliente y el servidor lo rechace.

  • Node.js acepta QUERY en su módulo http. Su parser integrado, llhttp, define HTTP_QUERY (consulta la copia de llhttp.h incluida en Node) y reconoce el método desde llhttp 9.2. Según el changelog de Node.js, esa versión del parser se incluyó en la v21.7.2, y está presente en la v22 y posteriores, así como en la v20.19.2. En esas versiones, http.METHODS incluye 'QUERY' y el handler del servidor recibe req.method === 'QUERY'.
  • El cliente HTTP de Go acepta cualquier token de método válido como cadena de método en http.NewRequest, por lo que ya puede enviar QUERY:
package main

import (
	"log"
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"category":"laptops","price":{"max":1500}}`)
	req, err := http.NewRequest("QUERY", "https://api.example.com/products/search", body)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
}
  • Laravel: Laravel 13.19.0 añade el método de cliente Http::query() y los helpers de testing query()/queryJson() para el verbo QUERY. Las rutas pueden aceptar QUERY mediante Route::match(), y los helpers de testing se incorporaron en el PR #60662 de laravel/framework. Un helper específico, Route::query(), se ha fusionado en la rama master de Laravel y no en la 13.x, por lo que ninguna versión 13.x lo incluye.

En el navegador, fetch() puede enviar QUERY desde un script en el mismo origen. La issue de Fetch que hace seguimiento de QUERY (whatwg/fetch#1938) señala que el Fetch Standard no prohíbe QUERY ni normaliza sus mayúsculas y minúsculas. Los navegadores envían la cadena del método exactamente como la escribes, así que utiliza siempre mayúsculas:

const res = await fetch('/products/search', {
  method: 'QUERY',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ category: 'laptops', price: { max: 1500 } }),
});
const data = await res.json();

¿Qué no admite QUERY todavía?

Actualmente, tres factores bloquean el método HTTP QUERY: los formularios HTML no pueden enviarlo, algunos servidores y capas de seguridad rechazan el método directamente y las llamadas de origen cruzado requieren una solicitud preflight.

Formularios. Los scripts pueden enviar QUERY con fetch(), pero los formularios no. La propuesta para admitir method="query" es la issue #12594 de WHATWG HTML. Sigue abierta y etiquetada como “needs implementer interest”. Hasta que se implemente, este marcado:

<form method="query" action="/products/search">
  <input name="category" value="laptops">
  <button>Search</button>
</form>

llega al servidor como un GET sin cuerpo, porque los navegadores tratan cualquier método de formulario desconocido como GET. Una reproducción en vivo creada para la issue lo demuestra.

Servidores, gateways y WAF. Cualquier salto que no reconozca el método puede rechazarlo. La misma reproducción señala que LiteSpeed, por ejemplo, responde a QUERY con un 400 antes de que se ejecute la aplicación. Es necesario comprobar por separado cada proxy inverso, balanceador de carga, CDN, API gateway y firewall.

Preflight de origen cruzado. QUERY no figura en la lista de métodos CORS-safelisted del Fetch Standard. Por lo tanto, una solicitud QUERY de origen cruzado siempre desencadena una solicitud preflight, independientemente de las cabeceras que incluya:

OPTIONS /products/search HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: QUERY
Access-Control-Request-Headers: content-type

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, QUERY
Access-Control-Allow-Headers: content-type

Si Access-Control-Allow-Methods no incluye QUERY, el navegador bloquea la solicitud real.

EntornoEstado
fetch(), mismo origenFunciona
fetch(), origen cruzadoFunciona tras un preflight que permita QUERY
Formularios HTMLRecurre a GET y descarta el cuerpo
Node.js httpFunciona (v20.19.2+, v21.7.2+, v22+; llhttp 9.2 reconoce QUERY)
Cliente HTTP de GoFunciona (cadena de método personalizada)
Laravel 13.19+Funciona (cliente, tests, Route::match)
LiteSpeedRechazado con 400 antes de que se ejecute la aplicación (según la reproducción de #12594)
Proxies, CDN, gateways, WAFDepende del producto y de su configuración

¿Debería GraphQL usar QUERY?

QUERY es adecuado para las operaciones de consulta (query) de GraphQL, que solo leen datos y encajan con un método seguro, idempotente y que transporta un cuerpo. Las mutaciones modifican el estado, por lo que deben seguir usando POST. Si estás evaluando ambos estilos de API, la explicación de GraphQL frente a REST analiza sus ventajas y desventajas. El argumento a favor de QUERY es el mismo en ambos casos: las lecturas de tipo búsqueda recuperan la caché y los reintentos a nivel HTTP.

Qué debe ocurrir para que QUERY funcione de extremo a extremo

Para que QUERY funcione en todas partes, cada capa entre el usuario y tu handler debe reconocerlo:

  1. La especificación HTML debe aceptar method="query" y los navegadores deben implementarlo.
  2. Cada salto (servidor de origen, proxy inverso, balanceador de carga, CDN, API gateway y WAF) debe interpretar y reenviar el método.
  3. Los frameworks deben enrutar QUERY hacia los handlers y parsear su cuerpo.
  4. Las cachés deben incluir el cuerpo de la solicitud y el Content-Type en la clave de caché antes de almacenar respuestas QUERY.

El preflight de CORS es intencionado: la RFC 10008 lo contempla y whatwg/fetch#1938 no solicita ningún cambio en la lista de métodos permitidos. Lo que sigue abierto en esa discusión es si la caché HTTP de Fetch, basada en la URL como clave, debería admitir claves de caché basadas en el cuerpo para QUERY.

Conclusión

QUERY corrige una incoherencia histórica de HTTP: las solicitudes de búsqueda pueden transportar un cuerpo sin renunciar a la caché ni a los reintentos seguros. Los scripts, los clientes de Go y las aplicaciones Laravel ya pueden utilizarlo. Los formularios no, y la infraestructura que no reconozca el método aún puede rechazarlo. Antes de migrar un endpoint, envía una solicitud QUERY real a través de toda tu ruta de producción, incluidos la CDN, el gateway y el WAF, y mantén el endpoint POST en funcionamiento hasta que todos los saltos la dejen pasar.

Preguntas frecuentes

¿Por qué no enviar simplemente un cuerpo de solicitud con GET en lugar de usar QUERY?

Un cuerpo en una solicitud GET no es fiable. La sección 9.3.1 de la RFC 9110 establece que el cuerpo de un GET no tiene un significado definido de forma general y no puede cambiar el destino ni el significado de la solicitud. Algunos servidores rechazan directamente este tipo de solicitudes, ya que un cuerpo en un GET puede utilizarse para request smuggling. Además, el cuerpo no forma parte de la clave de caché estándar. QUERY convierte el cuerpo en la propia consulta y lo incluye en la clave de caché.

¿Qué diferencia hay entre el método QUERY y el método SEARCH de WebDAV?

SEARCH es un método de WebDAV, definido en la RFC 5323 en 2008, para buscar recursos DAV mediante gramáticas de consulta XML. QUERY es un método HTTP de propósito general que admite cualquier formato de consulta. Ambos métodos son seguros, pero SEARCH nunca se extendió más allá de WebDAV. Los primeros borradores de la especificación de QUERY utilizaban el nombre SEARCH. Los autores lo cambiaron a QUERY porque SEARCH y los demás métodos seguros existentes provienen de WebDAV y dependen de un tipo de medio XML genérico, y porque el nombre QUERY coincide con la parte de consulta (query) de una URI.

¿Puedo documentar endpoints QUERY en OpenAPI?

Sí, a partir de OpenAPI 3.2.0, publicada en septiembre de 2025. Esa versión añade compatibilidad nativa con el método query, de modo que una operación QUERY puede declarar un esquema requestBody y sus respuestas igual que get o post. Los demás métodos no estándar se definen en el nuevo mapa additionalOperations. OpenAPI 3.0 y 3.1 no tienen ningún campo para QUERY, y la compatibilidad de las herramientas con la versión 3.2 varía de un generador a otro.

¿Qué código de estado devuelve un servidor que no admite QUERY?

Según la RFC 9110, un servidor que no reconoce un método de solicitud debería responder con 501 Not Implemented. Un servidor que reconoce QUERY pero no lo permite en un recurso concreto devuelve 405 Method Not Allowed, junto con una cabecera Allow que enumera los métodos que sí admite. Los proxies y los WAF pueden fallar de otras formas, por ejemplo con un simple 400, por lo que cualquier lógica de respaldo que recurra a POST debería contemplar los tres casos.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.