El Grupo de Trabajo HTTP del IETF ha especificado un nuevo método de petición HTTP llamado QUERY (draft-ietf-httpbis-safe-method-w-body, en camino de convertirse en RFC). QUERY cubre un hueco con el que se ha topado todo desarrollador REST: es una petición segura e idempotente, como GET, pero lleva un cuerpo de petición, como POST. sgcWebSockets ahora soporta QUERY de extremo a extremo, en los clientes HTTP/1.1, HTTP/2 y HTTP/3 y en los componentes de servidor, para Delphi y C++ Builder.
¿Por qué un nuevo método HTTP?
Hasta ahora había dos formas de enviar una consulta a un servidor, y ambas tienen problemas. Puedes codificar la consulta en la URL de una petición GET, pero las URL tienen límites prácticos de longitud, las consultas complejas son difíciles de escapar y la URL completa suele acabar en los registros de proxies y servidores, que es un mal lugar para valores de filtro sensibles. O puedes enviar la consulta en el cuerpo de una petición POST, pero POST no es seguro, ni idempotente, ni cacheable: un cliente HTTP no tiene permitido reintentarlo automáticamente tras un fallo de conexión, y las cachés no almacenarán la respuesta.
QUERY combina lo mejor de ambos. El contenido de la petición y su Content-Type definen la consulta, por lo que puede ser tan grande y tan estructurada como sea necesario (datos de formulario, JSON, SQL, JSONPath, lo que el servidor acepte). Al mismo tiempo, la especificación declara QUERY seguro e idempotente: nunca cambia el estado del recurso, puede reintentarse o repetirse con seguridad y sus respuestas son cacheables igual que una respuesta GET.
Cómo funciona QUERY
Una petición QUERY se parece a un POST con semántica de GET. Este ejemplo, adaptado de la especificación, pide a un recurso de contactos una proyección filtrada:
QUERY /contacts HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Accept: application/json
select=surname,givenname,email&limit=10
HTTP/1.1 200 OK
Content-Type: application/json
[
{ "surname": "Smith", "givenname": "John", "email": "smith@example.org" },
{ "surname": "Jones", "givenname": "Sally", "email": "sally.jones@example.com" }
]
Las reglas más importantes de la especificación:
- Toda petición QUERY debe incluir un
Content-Typeque coincida con su cuerpo. Un servidor debe rechazar la petición con un estado 4xx (400, 415 o 422) cuando la cabecera falta o es inconsistente con el contenido. - Las respuestas son cacheables. Una caché que las almacene debe incluir el cuerpo de la petición en la clave de caché, de modo que dos consultas distintas nunca colisionen.
- Las redirecciones conservan el método: tras un 301, 302, 307 o 308 el cliente repite el QUERY contra la nueva URI. La antigua excepción que reescribe un POST redirigido como un GET no se aplica a QUERY. Un 303 See Other es la forma explícita que tiene un servidor de decir "recupera el resultado con un GET desde esta Location".
- Un servidor puede anunciar qué formatos de consulta entiende un recurso con la nueva cabecera de respuesta
Accept-Query, y QUERY aparece en la cabeceraAllowde una respuesta OPTIONS cuando está soportado. - En los navegadores, QUERY no es un método de la lista segura de CORS, por lo que las llamadas entre orígenes desencadenan un preflight. Los valores por defecto de CORS del servidor OpenAPI de sgcWebSockets ya incluyen QUERY en
Access-Control-Allow-Methods.
QUERY desde el cliente HTTP/1.1
El cliente HTTP/1.1 incorpora los métodos Query y QueryAsync, que funcionan exactamente igual que sus equivalentes Post: pasa la URL y un stream con el cuerpo de la consulta, establece el Content-Type en la petición y lee el resultado.
uses
Classes, sgcHTTP;
var
oClient: TsgcHTTP1Client;
oQuery: TStringStream;
vResult: string;
begin
oClient := TsgcHTTP1Client.Create(nil);
try
oQuery := TStringStream.Create('select=surname,givenname,email&limit=10');
try
oClient.Request.ContentType := 'application/x-www-form-urlencoded';
oClient.Request.Accept := 'application/json';
vResult := oClient.Query('https://api.example.org/contacts', oQuery);
finally
oQuery.Free;
end;
finally
oClient.Free;
end;
end;
Como QUERY es idempotente, también encaja muy bien con las opciones de reintento del cliente: un QUERY que falla con un error de conexión puede reintentarse automáticamente sin el riesgo de duplicar un cambio de estado en el servidor. Los mismos métodos Query están disponibles en el cliente base de API REST, por lo que las integraciones de API personalizadas construidas sobre sgcWebSockets pueden adoptar el verbo sin trabajo adicional.
QUERY sobre HTTP/2 y HTTP/3
Los clientes HTTP/2 y HTTP/3 exponen el mismo verbo. Sobre HTTP/2 la petición se envía con la pseudocabecera :method establecida a QUERY y el cuerpo en frames DATA:
uses
Classes, sgcHTTP;
var
oClient: TsgcHTTP2Client;
oQuery: TStringStream;
vResult: string;
begin
oClient := TsgcHTTP2Client.Create(nil);
try
oQuery := TStringStream.Create('select=surname,givenname,email&limit=10');
try
vResult := oClient.Query('https://api.example.org/contacts', oQuery);
finally
oQuery.Free;
end;
finally
oClient.Free;
end;
end;
El cliente HTTP/3 ofrece una sobrecarga de tipo string con el content type form-urlencoded por defecto, lo que reduce las consultas simples a una sola llamada:
uses
sgcHTTP;
var
oClient: TsgcHTTP3Client;
vResult: string;
begin
oClient := TsgcHTTP3Client.Create(nil);
try
vResult := oClient.Query('https://api.example.org/contacts',
'select=surname,givenname,email&limit=10');
finally
oClient.Free;
end;
end;
Gestionar QUERY en el servidor
En el lado del servidor, TsgcWebSocketHTTPServer acepta peticiones QUERY sobre HTTP/1.1 y HTTP/2 y las envía al evento OnCommandOther, con el cuerpo de la petición ya leído y disponible en PostStream. Un manejador mínimo valida el Content-Type, ejecuta la consulta y devuelve el resultado:
procedure TForm1.ServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
vQuery: string;
begin
if SameText(ARequestInfo.Command, 'QUERY') then
begin
// the specification requires a Content-Type on every QUERY request
if (ARequestInfo.ContentType = '') or
not Assigned(ARequestInfo.PostStream) then
begin
AResponseInfo.ResponseNo := 400;
Exit;
end;
ARequestInfo.PostStream.Position := 0;
vQuery := ReadStringFromStream(ARequestInfo.PostStream);
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := RunContactsQuery(vQuery);
end;
end;
El servidor HTTP/3 lee los cuerpos QUERY de la misma forma, el servidor OpenAPI enruta las operaciones query declaradas en una especificación OpenAPI y anuncia el método en sus valores por defecto de CORS, y la función de reenvío HTTP hace de proxy de las peticiones QUERY con su cuerpo hacia un servidor backend.
Disponibilidad
El soporte de QUERY está incluido en sgcWebSockets 2026.7 en los clientes HTTP/1.1, HTTP/2 y HTTP/3, el cliente base de API REST, los componentes de servidor y el servidor OpenAPI, para Delphi 7 a Delphi 13 y C++ Builder. Como el formato de red subyacente es HTTP estándar, interopera desde hoy mismo con cualquier servidor o cliente que entienda el método. Puedes descargar la última versión desde la página de descarga de sgcWebSockets.
¿Preguntas, comentarios o ayuda con la migración? Ponte en contacto, te responderán las mismas personas que escribieron el código.
