El nuevo método HTTP QUERY en Delphi | eSeGeCe Blog

El nuevo método HTTP QUERY en Delphi

· Características

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:

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.